package com.fzu.geometa.validate.test;

import com.fzu.geometa.validate.function.CompareFunction;
import com.fzu.geometa.validate.function.DeepEqualFunction;
import com.fzu.geometa.validate.function.EmptyFunction;
import com.fzu.geometa.validate.util.XmlValidator;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.XPath;
import org.jaxen.FunctionContext;
import org.jaxen.XPathFunctionContext;
import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.StringWriter;

/**
 * IOS19163的数据验证
 * （不了不破坏原来的功能，从 ISO19163Test 中直接复制更改
 *  由 url 获取数据变为直接传入数据）
 */
public class ISO19163Validation {
    // 注册自定义 Xpath 函数
    static {
        FunctionContext instance = XPathFunctionContext.getInstance();
        XPathFunctionContext fc = (XPathFunctionContext) instance;
        fc.registerFunction(null, "empty", new EmptyFunction());
        fc.registerFunction(null, "deep-equal", new DeepEqualFunction());
        fc.registerFunction(null, "compare", new CompareFunction());
    }

    // 是否通过验证
    private boolean validated;

    // 测试文档
    private Document testDoc;

    // 报告验证结果
    private void xsdValidationTest() {

        if (validated) {
            Reporter.log("XML Schema validation passed");
        } else {
            Reporter.log("XML Schema validation failed");
            throw new RuntimeException("XML Schema validation failed");
        }
    }

    // 报告计算结果
    private void reportEvaluateResults(String expected, String actual) {
        Reporter.log("Expected: " + expected);
        Reporter.log("Actual: " + actual);
    }

    // xpath 计算
    private Object xpathEvaluate(String xpathExpr) {
        if (testDoc == null) {
            throw new RuntimeException("Test document not found");
        }
        XPath xpath = testDoc.createXPath(xpathExpr);
        Object res = xpath.evaluate(testDoc);
        return res;
    }

    // 非空测试
    private void notEmptyTest(String xpathExpr) {
        Reporter.log("XPath expression: " + xpathExpr);
        if (!xpathExpr.startsWith("empty")) {
            throw new RuntimeException("The outermost function must be 'empty'.");
        }
        // 计算结果
        Object res = xpathEvaluate(xpathExpr);
        reportEvaluateResults("false", res.toString());
        // 实际结果与预期结果对比
        Assert.assertEquals(res, false);
    }

    // 相等测试
    private void deepEqualTest(String xpathExpr) {
        Reporter.log("XPath expression: " + xpathExpr);
        if (!xpathExpr.startsWith("deep-equal")) {
            throw new RuntimeException("The outermost function must be 'deep-equal'.");
        }
        // 计算结果
        Object res = xpathEvaluate(xpathExpr);
        reportEvaluateResults("true", res.toString());
        // 实际结果与预期结果对比
        Assert.assertEquals(res, true);
    }

    // 比较测试（也是相等）
    private void compareTest(String xpathExpr) {
        Reporter.log("XPath expression: " + xpathExpr);
        if (!xpathExpr.startsWith("compare")) {
            throw new RuntimeException("The outermost function must be 'compare'.");
        }
        // 计算结果
        Object res = xpathEvaluate(xpathExpr);
        reportEvaluateResults("0", res.toString());
        // 实际结果与预期结果对比
        Assert.assertEquals(res, 0);
    }


    @BeforeTest(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim},
            description = "Parses url and xml document")
    @Parameters({"test-data"})
    public void init(String testData) throws IOException, DocumentException, SAXException {
        Reporter.log("Input data: \n" + testData);

        // 解析文档
        try {
            testDoc = DocumentHelper.parseText(testData);
        } catch (DocumentException e) {
            e.printStackTrace();
            Reporter.log("Failed to parse document");
            throw e;
        }

        // 创建验证器
        String schemaRefs = "/conf/schema-refs-19163.xml";
        StringWriter strWriter = new StringWriter();
        XmlValidator validator = null;
        try {
            validator = new XmlValidator(schemaRefs, strWriter);
        } catch (IOException | SAXException e) {
            e.printStackTrace();
            Reporter.log("Failed to create validator");
            throw e;
        }

        // 文档验证
        try {
            validated = validator.validateByXmlStr(testData);
        } catch (Exception e) {
            Reporter.log("RuntimeException" + e.getMessage());
            throw new RuntimeException(e);
        } finally {
            Reporter.log(strWriter.toString());
        }
    }


    @Test(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim}, priority = 1,
            description = "ImageAndGriddedData")
    public void req1() {
        Reporter.log("[req1: ImageAndGriddedData]");
        xsdValidationTest();
    }

    @Test(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus}, priority = 2,
            description = "Georectified")
    public void req2() {
        Reporter.log("[req2: Georectified]");
        String xpathExpr = "empty(//*[local-name()='IE_Georectified'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = DataType.sim, priority = 3,
            description = "Georeferenceable")
    public void req3() {
        Reporter.log("[req3: Georeferenceable]");
        String xpathExpr = "empty(//*[local-name()='IE_Georeferenceable'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = {DataType.cat, DataType.num}, priority = 4,
            description = "ThematicGriddedData")
    public void req4() {
        Reporter.log("[req4: ThematicGriddedData]");
        xsdValidationTest();
    }

    @Test(groups = DataType.cat, priority = 5,
            description = "CategoricalGriddedData")
    public void req5() {
        Reporter.log("[req5: CategoricalGriddedData]");
        String xpathExpr = "empty(//*[local-name()='IE_CategoricalGriddedData'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = DataType.num, priority = 6,
            description = "NumericalGriddedData")
    public void req6() {
        Reporter.log("[req6: NumericalGriddedData]");
        String xpathExpr = "empty(//*[local-name()='IE_NumericalGriddedData'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = {DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim}, priority = 7,
            description = "Imagery")
    public void req7() {
        Reporter.log("[req7: Imagery]");
        xsdValidationTest();
    }

    @Test(groups = {DataType.fus, DataType.sim}, priority = 8,
            description = "SyntheticImage")
    public void req8() {
        Reporter.log("[req8: SyntheticImage]");
        xsdValidationTest();
    }

    @Test(groups = DataType.fus, priority = 9,
            description = "FusedImage")
    public void req9() {
        Reporter.log("[req9: FusedImage]");
        String xpathExpr = "empty(//*[local-name()='IE_FusedImage'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = DataType.sim, priority = 10,
            description = "SimulatedImage")
    public void req10() {
        Reporter.log("[req10: SimulatedImage]");
        String xpathExpr = "empty(//*[local-name()='IE_SimulatedImageData'])";
        notEmptyTest(xpathExpr);

    }

    @Test(groups = DataType.opt, priority = 11,
            description = "OpticalImage")
    public void req11() {
        Reporter.log("[req11: OpticalImage]");
        String xpathExpr = "empty(//*[local-name()='IE_OpticalImage'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = {DataType.sar, DataType.rad}, priority = 12,
            description = "MicrowaveData")
    public void req12() {
        Reporter.log("[req12: MicrowaveData]");
        xsdValidationTest();
    }

    @Test(groups = DataType.sar, priority = 13,
            description = "ActiveMWData")
    public void req13() {
        Reporter.log("[req13: ActiveMWData]");
        xsdValidationTest();
    }

    @Test(groups = DataType.rad, priority = 14,
            description = "PassiveMWData")
    public void req14() {
        Reporter.log("[req14: PassiveMWData]");
        xsdValidationTest();
    }

    @Test(groups = DataType.sar, priority = 15,
            description = "SARData")
    public void req15() {
        Reporter.log("[req15: SARData]");
        String xpathExpr = "empty(//*[local-name()='IE_SARData'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = DataType.rad, priority = 16,
            description = "RadiometerData")
    public void req16() {
        Reporter.log("[req16: RadiometerData]");
        String xpathExpr = "empty(//*[local-name()='IE_RadiometerData'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim}, priority = 17,
            description = "CoverageType")
    public void req17() {
        Reporter.log("[req17: CoverageType]");
        String xpathExpr = "empty(//*[local-name()='RectifiedGridCoverage']) and empty(//*[local-name()='ReferenceableGridCoverage']) and empty(//*[local-name()='GridCoverage'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim}, priority = 18,
            description = "multipart")
    public void req18() {
        Reporter.log("[req18: multipart]");
        xsdValidationTest();
    }

    @Test(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus}, priority = 19,
            description = "GeorectifiedCoherence")
    public void req19() {
        Reporter.log("[req19: GeorectifiedCoherence]");
        String xpathExpr = "empty(//*[local-name()='IE_Georectified']) or empty(//*[local-name()='RectifiedGridCoverage'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = DataType.sim, priority = 20,
            description = "GeoreferenceableCoherence")
    public void req20() {
        Reporter.log("[req20: GeoreferenceableCoherence]");
        String xpathExpr = "empty(//*[local-name()='ReferenceableGridCoverage']) or empty( //*[local-name()='IE_Georeferenceable'])";
        notEmptyTest(xpathExpr);
    }

    @Test(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim}, priority = 21,
            description = "CRSCoherence")
    public void req21() {
        Reporter.log("[req21: CRSCoherence]");
        String xpathExpr = "deep-equal(//gml:Envelope/@srsName,//gml:Point/@srsName)";
        deepEqualTest(xpathExpr);

    }

    @Test(groups = {DataType.cat, DataType.num, DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim}, priority = 22,
            description = "locationCoherence")
    public void req22() {
        Reporter.log("[req22: locationCoherence]");
        String xpathExpr = "compare(string(//*[local-name()='Envelope']/@srsDimension),string(//*[local-name()='RectifiedGrid']/@dimension))";
        compareTest(xpathExpr);

    }

    @Test(groups = {DataType.opt, DataType.sar, DataType.rad, DataType.fus, DataType.sim}, priority = 23,
            description = "rangeCoherence")
    public void req23() {
        Reporter.log("[req23: rangeCoherence]");
        String xpathExpr = "compare(string(count(//*[local-name()='DataRecord']/*[local-name()='field'])), string(//*[local-name()='numberofBands']))";
        compareTest(xpathExpr);
    }

}
